package foop14bsp1.controller;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import foop14bsp1.communication.CommunicationCommand;
import foop14bsp1.gui.ServerInfoFrame;
import foop14bsp1.model.GameBlockType;
import foop14bsp1.model.GameField;
import foop14bsp1.helper.Levels;

public class Server extends Thread {

	private ServerInfoFrame serverFrame;
	private ServerSocket serverSocket;
	private GameField gameField;
	private Socket[] sockets;
	private ServerWorker[] workers;
	private int numPlayers;
	private GameLogicProcessor logicProcessor;
	private int actualLevel;

	public Server(int port) {
		actualLevel = 1;
		this.gameField = new GameField(actualLevel);

		try {
			this.serverSocket = new ServerSocket(port, 2);
		} catch (IOException e) {
			// e.printStackTrace();
			System.err.println("MouseServer was already created!");
			return;
		}

		// if no server exists show ServerFrame
		this.serverFrame = new ServerInfoFrame();
		this.serverFrame.setVisible(true);

		this.serverFrame.log("MouseServer successfully created!");

		this.numPlayers = serverFrame.getPlayerNumber();
		this.sockets = new Socket[numPlayers];
		this.workers = new ServerWorker[numPlayers];
	}

	public void run() {
		if (serverSocket != null) {
			this.waitForPlayers();
			
			this.startGame();
		}
	}

	private void startGame() {
		// send startgame command and number of players to all connected clients
			byte[] startCommand = {
					(byte) CommunicationCommand.STARTGAME.ordinal(),
					(byte) numPlayers };
			for (int i = 0; i < this.sockets.length; i++) {
				try {
					this.sockets[i].getOutputStream().write(startCommand, 0, startCommand.length);
				} catch (IOException e) {
					// e.printStackTrace();
					System.err
							.println("Error: Could not send start game command to player "
									+ (i + 1) + ": " + e.getMessage());
					this.serverFrame
							.log("Error: Could not send start game command to client "
									+ (i + 1) + ": " + e.getMessage());
					this.serverFrame.requestFocus();
					return;
				}
			}

			/**
			 * Start Server workers for all connected players, that receive door
			 * state change commands from them.
			 */
			for (ServerWorker worker : this.workers) {
				worker.start();
			}
			
			//wait 5 seconds to start
			for(int i = 5; i > 0; i--) {
				try {
					serverFrame.log("Next Game starts in " + i + " seconds!");
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					System.err.println("ERROR while sleeping: " + e.getMessage());
				}
			}

			/**
			 * Start game logic processor, which will calculate mouse positions
			 * and send it to all players
			 */
			this.logicProcessor = new GameLogicProcessor(this.gameField,
					this.sockets, this);
			this.logicProcessor.start();
	}

	/**
	 * wait for 'playerNumber' connections so game can be played
	 */
	private void waitForPlayers() {
		for (int i = 0; i < numPlayers; i++) {
			serverFrame.log("Waiting for " + (numPlayers - i) + " players:");
			try {
				this.sockets[i] = serverSocket.accept();
				this.sockets[i].getOutputStream().write(i + 1); // send
																// playernumber
				this.serverFrame.log("Player " + (i + 1) + " connected:");

				this.workers[i] = new ServerWorker(this.sockets[i], this);

			} catch (IOException e) {
				// e.printStackTrace();
				System.err.println("Error: Connection to player " + (i + 1)
						+ " was not successfull: " + e.getMessage());
				this.serverFrame.log("Error: Connection to player " + (i + 1)
						+ " was not successfull: " + e.getMessage());
				this.serverFrame.requestFocus();
				return;
			}
		}
		this.serverFrame.log("All " + numPlayers
				+ " players connected, starting game!");

		// close server socket, because it is not needed anymore
		try {
			this.serverSocket.close();
		} catch (IOException e) {
			// e.printStackTrace();
			System.err
					.println("Error: Could not close ServerSocket after all clients connected: "
							+ e.getMessage());
		}
	}

	/**
	 * Changes the state of a door at position of params col and row and calls
	 * informPlayersAboutStateChange.
	 */
	public synchronized void changeDoorState(int col, int row)
			throws IOException {
		if (this.gameField.get(row, col) == GameBlockType.CLOSEDDOOR) {
			this.gameField.set(GameBlockType.OPENDOOR, row, col);
			this.informPlayersAboutDoorStateChange(GameBlockType.OPENDOOR, row,
					col);
		} else if (this.gameField.get(row, col) == GameBlockType.OPENDOOR) {
			this.gameField.set(GameBlockType.CLOSEDDOOR, row, col);
			this.informPlayersAboutDoorStateChange(GameBlockType.CLOSEDDOOR,
					row, col);
		} else {
			System.err
					.println("Error: Tried to set a DoorState on block that is no door: "
							+ this.gameField.get(row, col)
							+ ", at position: ("
							+ col + "," + row + ").");
		}
	}

	/**
	 * Iterates through connections with all clients and sends them a
	 * notification about a new doorstate of param newState at a given position
	 * of params row and col.
	 */
	private void informPlayersAboutDoorStateChange(GameBlockType newState,
			int row, int col) throws IOException {
		for (Socket s : this.sockets) {
			byte[] notification = new byte[9];
			notification[0] = (byte) CommunicationCommand.NEWDOORSTATE.ordinal();
			notification[1] = (byte) newState.ordinal();
			notification[2] = (byte) col;
			notification[3] = (byte) row;
			
			System.out.println("inform players: doorstate changed: " + CommunicationCommand.NEWDOORSTATE.ordinal() + " " + newState.ordinal() + " " + col + ", " + row);
			s.getOutputStream().write(notification, 0, notification.length);
			s.getOutputStream().flush();
		}
	}
	
	public void playerWon(int player) {
		serverFrame.log("Player " + player + " won the game!");
		this.informPlayersAboutWin(player);
		
		if(actualLevel < Levels.levelCount) {
			this.gameField = new GameField(++actualLevel);
			
			//Wait 10 seconds to start again
			try {
				serverFrame.log("Next Game starts in " + 10 + " seconds!");
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				System.err.println("ERROR while sleeping: " + e.getMessage());
			}
			
			this.logicProcessor = new GameLogicProcessor(this.gameField,
					this.sockets, this);
			this.logicProcessor.start();
		}
	}
	
	private void informPlayersAboutWin(int player) {
		System.out.println("inform players that player " + player + " won the game!");
		
		try {
			for (Socket s : this.sockets) {
				byte[] notification = new byte[9];
				notification[0] = (byte) CommunicationCommand.PLAYERWON.ordinal();
				notification[1] = (byte) player;
				
				s.getOutputStream().write(notification, 0, notification.length);
				s.getOutputStream().flush();
			}
		} catch(IOException e) {
			System.err
			.println("Error: Could not send playerwon to client: "
					+ e.getMessage());
		}
	}
}
